/*
	@ 一笔画识别图片插件
*/

export default class OneStrokePlugin {
  name: any;
  constructor() {
    // 没什么事做
  }

  // 图片转换成对应格式
  parse(img, name) {
    return new Promise((resolve, reject) => {
      this.name = name;
      // 字符串
      if (typeof img === "string") {
        const src = img;
        img = new Image();
        img.crossOrigin = "*";
        img.src = src;
      }
      // 图片对象
      if (img instanceof Image === true) {
        // 已经加载完成
        if (img.complete === true) resolve(this.scan(img));
        // 未加载完成等待
        else {
          img.onload = () => resolve(this.scan(img));
          img.onerror = (err) => reject(err);
        }
      }
    });
  }

  /*
		@ 扫描识别线段
		@ 考虑到图片可能来自截图，所以添加两个参数：head & foot 表示图像的头与尾的占位
	*/
  scan(img, resolution = 4, head = 120, foot = 120) {
    head = (head / resolution + 1) >> 0;
    foot = (foot / resolution + 1) >> 0;
    // 颜色集
    let colors = new Map();
    let canvas = document.createElement("canvas");
    const ctx = canvas.getContext("2d");
    // 宽度是固定的750
    let width = 750;
    // img.with / width 的比率
    const ratio = width / img.width;
    // 高度计算
    let height = img.height * ratio;

    // 按分辨率压缩
    width = (width / resolution) >> 0;
    height = (height / resolution) >> 0;
    canvas.width = width;
    canvas.height = height;

    ctx.drawImage(img, 0, 0, width, height);

    // imageData
    let imageData = ctx.getImageData(0, 0, width, height);
    let data = imageData.data;

    // 起始索引
    const startIndex = head * width * 4;
    // 终点索引
    const endIndex = data.length - foot * width * 4;

    /*
			@ 收集颜色
			@ 扫描图像并收集图像的所有颜色
			@ 由于底色被认定为是白色或透明，所以白色和透明不收集
			@ 考虑到噪点对图像的影响，所以把接近白色的点也视作白色
		*/
    // 线段颜色
    let lineColor = [0, 0, 0, 0, 0];

    // 端点颜色
    let vertexColor = [0, 0, 0, 0, 0];

    // 判断是否属于线段
    const isBelongLine = (r, g, b, a) => {
      return (
        Math.abs(lineColor[0] - r) <= 10 &&
        Math.abs(lineColor[1] - g) <= 10 &&
        Math.abs(lineColor[2] - b) <= 10
      );
    };

    // 判断是否属于端点
    const isBelongVertex = (r, g, b, a) => {
      return (
        Math.abs(vertexColor[0] - r) <= 10 &&
        Math.abs(vertexColor[1] - g) <= 10 &&
        Math.abs(vertexColor[2] - b) <= 10
      );
    };

    // 判断是否属于底色
    const isBelongBackground = (r, g, b, a) => {
      return 255 - r <= 20 && 255 - g <= 20 && 255 - b <= 20 && 255 - a <= 20;
    };

    // 扫描像素
    for (let i = startIndex; i < endIndex; i += 4) {
      const r = data[i],
        g = data[i + 1],
        b = data[i + 2],
        a = data[i + 3];
      // 过滤白色/透明/接近白色
      if (a === 0 || isBelongBackground(r, g, b, a)) {
        continue;
      }
      const rgba = "rgba(" + r + "," + g + "," + b + "," + a + ")";
      if (colors.has(rgba) === true) {
        const color = colors.get(rgba);
        color[4]++;
      } else {
        colors.set(rgba, [r, g, b, a, 1]);
      }
    }

    // 颜色最多的是线段的颜色
    for (const color of colors.values()) {
      const countA = color[4],
        countB = lineColor[4];
      if (countA > countB) lineColor = color;
    }

    // 颜色第二多的是点的颜色
    for (const color of colors.values()) {
      const [r, g, b, a] = [color[0], color[1], color[2], color[3]];
      if (isBelongLine(r, g, b, a)) {
        continue;
      }
      const countA = color[4],
        countB = vertexColor[4];
      if (countA > countB) vertexColor = color;
    }

    /*
			@ 收集图像中的端点
			@ 为了方便处理，把端点视作矩形（rect)
		*/
    const vertexes = [];
    const collect = (index) => {
      // 端点的边界 --- 用一个 rect 表示
      // eslint-disable-next-line prefer-const
      let top = (index / (width * 4) + 1) >> 0,
        right = ((index % (width * 4)) / 4) >> 0,
        bottom = top,
        left = right;
      // RGBA
      let r = data[index],
        g = data[index + 1],
        b = data[index + 2],
        a = data[index + 3];
      while (isBelongVertex(r, g, b, a)) {
        // 删除水平方向的点
        const boundary = clearHorizontal(index);
        left = Math.min(left, boundary.left);
        right = Math.max(right, boundary.right);
        // 点往下移动
        index += width * 4;
        (r = data[index]),
          (g = data[index + 1]),
          (b = data[index + 2]),
          (a = data[index + 3]);
        // bottom 像素加1
        ++bottom;
      }
      // 将点存入 vertexes
      vertexes.push({ top, right, bottom, left });
    };

    /*
			@ 清空指定点左右同色的像素
		*/
    const clearHorizontal = (index) => {
      // 左坐标
      let leftIndex = index - 4;
      // 向左
      while (
        isBelongVertex(
          data[leftIndex],
          data[leftIndex + 1],
          data[leftIndex + 2],
          data[leftIndex + 3]
        )
      ) {
        // 把 a 设置为 0 表示清除
        data[leftIndex + 3] = 0;
        // 向左移动
        leftIndex -= 4;
      }
      // 右坐标
      let rightIndex = index;
      // 向右
      while (
        isBelongVertex(
          data[rightIndex],
          data[rightIndex + 1],
          data[rightIndex + 2],
          data[rightIndex + 3]
        )
      ) {
        // 把 a 设置为 0 表示清除
        data[rightIndex + 3] = 0;
        // 向左移动
        rightIndex -= 4;
      }

      const left = ((leftIndex % (width * 4)) / 4) >> 0,
        right = ((rightIndex % (width * 4)) / 4) >> 0;
      return { left, right };
    };

    // 矩形相交
    const isRectCross = (rectA, rectB) => {
      const { top: topA, right: rightA, bottom: bottomA, left: leftA } = rectA;
      const { top: topB, right: rightB, bottom: bottomB, left: leftB } = rectB;
      // 判断垂直方向是否具备相交条件
      if (
        (topA <= topB && bottomA >= topB) ||
        (topB <= topA && bottomB >= topA)
      ) {
        // 判断水平方向是否具备相交条件
        if (
          (leftA <= leftB && rightA >= leftB) ||
          (leftB <= leftA && rightB >= leftA)
        ) {
          return true;
        }
      }
      return false;
    };

    // 合并矩形
    const mergeRect = (rectA, rectB) => {
      return {
        top: Math.min(rectA.top, rectB.top),
        right: Math.max(rectA.right, rectB.right),
        bottom: Math.max(rectA.bottom, rectB.bottom),
        left: Math.min(rectA.left, rectB.left),
      };
    };

    // 扫描图像
    for (let i = startIndex; i < endIndex; i += 4) {
      const r = data[i],
        g = data[i + 1],
        b = data[i + 2],
        a = data[i + 3];
      // 过滤白色/透明/接近白色
      if (a === 0 || isBelongBackground(r, g, b, a)) {
        continue;
      }
      // 遇到端点
      else if (isBelongVertex(r, g, b, a)) {
        // 收集端点
        collect(i);
      }
    }

    // 由于噪点的影响 vertexes 并不精准，需要进行一次归并
    for (let i = 0; i < vertexes.length - 1; ++i) {
      let rectA = vertexes[i];
      // 跳过被删除的节点
      if (!rectA) continue;
      for (let j = 0; j < vertexes.length; ++j) {
        const rectB = vertexes[j];
        // 跳过被删除的节点
        if (i === j || !rectB) continue;
        // 矩形相交
        if (isRectCross(rectA, rectB) === true) {
          // 合并矩形
          rectA = vertexes[i] = mergeRect(rectA, rectB);
          // 删除 rectB
          delete vertexes[j];
        }
      }
    }

    // 端点的中心坐标
    const coords = [];
    // 端点的半径
    let radius = 0;
    // 过滤空洞
    vertexes.forEach((rect) => {
      const w = rect.right - rect.left,
        h = rect.bottom - rect.top;
      // 半径取最大值
      radius = Math.max(radius, w / 2, h / 2) >> 0;
      coords.push([(rect.left + w / 2) >> 0, (rect.top + h / 2) >> 0]);
    });

    // 最终的线段
    const lines = [];

    /*
			@ 扫描两点之间是否存在线段
			@ 思路：均分断点
			@ AB间均分为 n 段，此时 A ---> B 之间均匀分布着 n - 1 个点
			@ 校验这n个点是否属于线段
		*/

    for (let i = 0, len = coords.length; i < len - 1; ++i) {
      const aX = coords[i][0],
        aY = coords[i][1];
      for (let j = i + 1; j < len; ++j) {
        const bX = coords[j][0],
          bY = coords[j][1];
        // AB 的距离
        const distance = Math.sqrt(Math.pow(aX - bX, 2) + Math.pow(aY - bY, 2));
        // AB 均分为 n 个子线段，每个子线段的长度不得大于端点的直径，避免漏扫描
        let n = (distance / (2 * radius)) >> 0;
        // 子线段的步长（分X与Y）
        const stepX = (bX - aX) / n,
          stepY = (bY - aY) / n;
        while (--n > 0) {
          const index =
            ((aX + stepX * n) >> 0) * 4 + ((aY + stepY * n) >> 0) * width * 4;
          const [r, g, b, a] = [
            data[index],
            data[index + 1],
            data[index + 2],
            data[index + 3],
          ];
          // 断点没有落在线段上
          if (!isBelongLine(r, g, b, a)) break;
        }

        // 被检验的点都在线段上，表示 AB 成线
        if (0 === n) {
          // 还原尺寸
          lines.push({
            x1: coords[i][0] * resolution,
            y1: coords[i][1] * resolution,
            x2: coords[j][0] * resolution,
            y2: coords[j][1] * resolution,
          });
        }
      }
    }

    // 删除对象
    canvas = colors = imageData = data = null;
    // return {vertexColor, lineColor, lines};
    // 端点颜色
    const baseVertexColor =
      vertexColor[0] * Math.pow(256, 2) +
      vertexColor[1] * Math.pow(256, 1) +
      vertexColor[2];
    // 线段颜色
    const baseLineColor =
      lineColor[0] * Math.pow(256, 2) +
      lineColor[1] * Math.pow(256, 1) +
      lineColor[2];
    // 手绘线的颜色取端点的半色值
    const strokeColor =
      (vertexColor[0] * Math.pow(256, 2)) / 2 +
      (vertexColor[1] * Math.pow(256, 1)) / 2 +
      vertexColor[2] / 2;

    return {
      lineColor: baseLineColor,
      vertexColor: baseVertexColor,
      strokeColor: strokeColor,
      activeVertexColor: baseVertexColor,
      lines: lines,
    };
  }
}
